# Copyright (c) 2011, Chandler Armstrong (omni dot armstrong at gmail dot com)
# see LICENSE.txt for details



"""vizier sprite management and sprite object"""



from __future__ import division
from math import cos, sin, radians
import os

from numpy import array, dot
import pygame
from pygame import Rect, sprite, Surface, transform



# look up tables (LUTs)
_COS_TABLE = dict([(deg, cos(radians(deg))) for deg in xrange(360)])
_SIN_TABLE = dict([(deg, sin(radians(deg))) for deg in xrange(360)])
# cache
_OFFSET_CACHE = {}
_IMAGE_CACHE = {}

COLORKEY = (0, 0, 0)



def update_path(path, center=None, offset=None):
    """
    Initialize image from disk.

    arguments:
    path -- path to image on disk, where path is a list of path components that
        will be passed to os.path.join

    keyword arguments:
    center -- position to set as center of image, for rotation purposes.

    return key for sprite initialization
    """
    if not (center is None or offset is None): raise ValueError(
        'Can specify either center or offset, not both')
    offset_cache = _OFFSET_CACHE
    image_cache = _IMAGE_CACHE
    image, key = load_path(path)

    # set offset
    if not center is None:
        img_x, img_y = image.get_rect().center
        spr_x, spr_y = center
        # offset is center from anchor, so that center may be translated as
        # rotating around anchor
        offset = array([[img_x - spr_x], [img_y - spr_y]])

    offset_cache[key] = offset

    # rotate and set image
    image_cache[key] = {}
    image_cache[key][0] = image
    for angle in xrange(1, 360):
        # -angle so image rotation is CW because rotation matrices, unmodified,
        # will result in CW rotation since disply is in CW angles
        img_rot = transform.rotate(image, -angle)
        AABB = img_rot.get_bounding_rect()
        surf_rot = Surface(AABB.size)
        surf_rot.set_colorkey(COLORKEY)
        surf_rot.blit(img_rot, (0, 0), area=AABB)
        image_cache[key][angle] = surf_rot

    return key


def update_paths(*a):
    """
    Initialize images from disk.

    arguments:
    *a -- paths to images on disk, where each path is a list of path components
      that will be passed to os.path.join

    return list of keys for sprite initialization, keys are in the order
      that the paths were provided to update paths
    """
    return [update_path(path) for path in a]


def load_path(path):
    """
    accept a path and load and return a new image from the hard disk

    return None

    this method will not prepare vizier.Sprite to accept the image key
    """
    #key = os.path.join(*path)
    key = os.sep.join(path)
    image = pygame.image.load(key)
    image = image.convert()
    image.set_colorkey(COLORKEY)
    return image, key



class Sprite(sprite.Sprite):
    """
    Base vizier.Sprite object, intended to be subclassed by user designed sprite

    attributes:
    image_key -- the key to the sprite's image in the image_cache.
    image -- sprite image; will change as sprite is zoomed/rotated.
    rect -- sprite rect in world space; see help(Viewer) for details.
    vzrect -- sprite rect in node space; see help(Viewer) for details.
    node -- the node the sprite is on; see help(Viewer) for details.
    P -- a list of points in worldspace, these are converted to nodespace and
      placed in vzP by update1
    vzP -- a list of points in nodespace, these are converted to worldspace and
      placed in P by update0
    angle -- rotation angle
    """


    def __init__(self, image_key, pos, node):
        """
        vizier.Sprite constructor

        arguments:
        image_key -- the key to the sprite's image in the image_cache.
        pos -- the topleft position of the sprite on the node.
        node -- the node the sprite is on; see help(Viewer) for details.
        """
        sprite.Sprite.__init__(self)

        image = _IMAGE_CACHE[image_key][0]
        size = image.get_rect().size

        self.offset = _OFFSET_CACHE[image_key]
        self.image_key = image_key
        self.image = image
        self.rect = Rect((0, 0), size)
        self.vzrect = Rect(pos, size)
        self.node = node
        self.P = {}
        self.vzP = {}
        self.angle = 0


    def __getstate__(self):
        odict = self.__dict__.copy()
        del odict['image']
        return odict


    def __setstate__(self, ndict):
        self.__dict__.update(ndict)
        update_path([self.image_key], offset=self.offset)
        self.image = _IMAGE_CACHE[self.image_key][self.angle]


    def _rotate(self, x0, deg, origin=None):
        if not origin: origin = array([[0],[0]])
        else: origin = origin.reshape(2, 1)

        x0 = x0.reshape(2, 1)
        x0 -= origin

        costheta = _COS_TABLE[deg]
        sintheta = _SIN_TABLE[deg]
        A = array([[costheta, -sintheta], # rotation matrix
                   [sintheta, costheta]])

        return dot(A, x0) + origin


    def rotate(self, angle_delta=0):
        """
        Rotate sprite by angle_delta degrees.

        angle_delta -- degree change in rotation angle

        return None
        """
        angle = self.angle
        angle += angle_delta
        angle %= 360
        angle_delta %= 360
        self.angle = angle

        rect = self.rect
        offset = self.offset

        if not offset is None:
            rect.move_ip(tuple(-offset))
            offset = self._rotate(offset, angle_delta)
            rect.move_ip(offset)
            self.offset = offset

        image = _IMAGE_CACHE[self.image_key][angle]
        size = image.get_rect().size
        center = rect.center
        rect.size = size
        rect.center = center

        # update1 will reposition vzrect from rect
        self.image, self.vzrect.size = image, size
